package org.jeecg.modules.miniAppCustomerEndPoint.controller;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.hc.entity.HcWxPayRefund;
import org.jeecg.modules.hc.enums.HcWxPayStateEnum;
import org.jeecg.modules.miniAppCustomerEndPoint.constant.JxRedisConstant;
import org.jeecg.modules.miniAppCustomerEndPoint.entity.JxOrderDo;
import org.jeecg.modules.miniAppCustomerEndPoint.entity.JxUserDo;
import org.jeecg.modules.miniAppCustomerEndPoint.entity.JxWxPayVo;
import org.jeecg.modules.miniAppCustomerEndPoint.enums.JxOrderStateEnum;
import org.jeecg.modules.miniAppCustomerEndPoint.mapper.JxOrderMapper;
import org.jeecg.modules.miniAppCustomerEndPoint.mapper.JxUserMapper;
import org.jeecg.modules.miniAppCustomerEndPoint.service.JxOrderServiceImpl;
import org.jeecg.modules.miniAppCustomerEndPoint.service.JxWxPayServiceImpl;
import org.jeecg.modules.miniAppCustomerEndPoint.utils.JxUserUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

@Api(tags = "订单")
@RestController
@RequestMapping("/jx/jxOrderPay")
@Slf4j
public class JxOrderPayController {

    @Autowired
    private JxWxPayServiceImpl wxPayServiceImpl;

    @Autowired
    private JxOrderServiceImpl jxOrderServiceImpl;

    @Autowired
    private JxOrderMapper jxOrderMapper;

    @Autowired
    private JxUserMapper jxUserMapper;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private WxPayService payService;

    /**
     * 购物车页面，点击【立即下单】 跳转到 订单确认页面，然后点击 【提交订单】，调用此接口，传递如下信息
     * （1）用户地址，包括站点id，取餐地址（大厦+楼层+门牌号）
     * （2）用户挑选的菜品列表，包括数量，规格
     * （3）使用的优惠券
     * （4）总订单金额
     * （5）备注
     * {
     *     "siteId":"1693633601729568769",
     *     "userId": 11111,
     *     "nameTakingMeal": "皮皮喵",
     *     "phoneTakingMeal": "15011111111",
     *     "address":"xx科技园-xx大厦xx栋-xx层-xx取餐点",
     *     "shoppingCartIds":["shoppingcartId1", shoppingcartId2", shoppingcartId3"]
     *     "couponIds":"xxxxx",
     *     "couponValue": 150, // 优惠券的优惠金额
     *     "totalPrice": 1600, // 最终支付的费用
     *     "note": "不吃香菜"
     * }
     *
     * @return
     */
    @PostMapping("/createOrder")
    public Result<?> createOrder(
            HttpServletRequest request,
            @RequestBody() JSONObject requestBody) {

        log.info("创建订单 请求参数: {}", requestBody);
        String token = request.getHeader("X-Access-Token");
        // todo request body 中的userId 不是必选项，只是调试时使用，正式上线时，需要去掉；
        // todo 可以使用其他人的用户id进行调试，每个用户使用微信登录时，才能获取到该用户的id，此处可以绕过此限制
        String userIdForDebug = requestBody.getString("userIdForDebug");
        String userId = JxUserUtil.getUserId(token, userIdForDebug);

        String siteId = requestBody.getString("siteId");
        JSONArray shoppingCartIds = requestBody.getJSONArray("shoppingCartIds");
        List<JSONObject> outStockFoods = new ArrayList<>();
        for (int i = 0 ; i < shoppingCartIds.size(); i ++) {
            // foodItem 格式 --> [ 店铺id， 菜品id， 菜品规格列表， 菜品销售价格]
            // foods数组中，每一个元素 代表一个购物车中的 记录id
            String shoppingCartId = shoppingCartIds.getString(i);
            // 1. 对用户选购的菜品是否有库存进行检查
            // 检查用户购买的商品，是否还有库存；每个店铺使用一个hash类型的kv对存放店铺中各个菜品的库存
            // store_xxx_stock -> {"food1": 10, "food2":20, "food3": 3, "food4": 0}; 每天店铺停止营业时，将 该店铺的对应的kv对中，value部分的所有 库存复位到 限购值
            jxOrderServiceImpl.checkFoodStock(outStockFoods, siteId, shoppingCartId);

            // 2. todo 对价格进行核对,对每个菜品的价格进行累加，尤其是自选快餐，然后再减去优惠券的值，尤其是判断【限期使用才优惠的优惠券】是否可以使用
            // todo 使用的优惠券，使用请求参数中的 receiveCouponIds 字段的值，接口参数做了调整，之前的 couponIds 废弃了
            jxOrderServiceImpl.checkOrderPrice();
        }
        // 当某些商品无库存时，返回错误码以及没有库存的菜品id列表，小程序端需要弹窗提示 某些菜品无库存，需要提示用户 重新购买其他商品
        if (outStockFoods.size() > 0) {
            return Result.error(450, outStockFoods.toString() + " 没有库存，请选择其他菜品", outStockFoods);
        }
        try {
            // 向 jx_order 表中添加一条记录，向 jx_order_detail 表添加一条或多条记录，订单中的每个菜品，在 jx_order_detail 中对应一条记录
            String orderId = jxOrderServiceImpl.insertOrderRecords(userId, requestBody);
            JxUserDo jxUserDo = jxUserMapper.selectById(userId);
            JxWxPayVo jxWxPayVo = wxPayServiceImpl.pay( userId, jxUserDo.getOpenId(), orderId,"ppm的红包",  1);

            // todo 先将下单接口返回的 预支付信息写入到db中
            // 再写入到redis中，设置有效期为2天；当在 callback中确认用户已支付完成后，也可以提前删除redis中的 预支付数据
            String wxPrePayInfo = JSONObject.toJSONString(jxWxPayVo);
            redisUtil.set(JxRedisConstant.JX_ORDER_PREPAY_INFO + orderId, wxPrePayInfo, 2 * JxRedisConstant.DAY_IN_SECONDS);
            // 将 购物车中的菜品 标记删除，将 del_flag 设置为 1; 并将购物车中菜品项所属订单id，回填到购物项的 orderId，此字段更多的是用来调试，此字段对于业务方面的作用较小
            jxOrderServiceImpl.deleteShoppingCartItemsByFlag(orderId, shoppingCartIds);
            // 当前由于使用的域名时btprint.yongshantuan.cn，回调接口也是使用此域名，导致一直无法接收到回调，当前暂时通过异步查询线程，最多查询5分钟，来查询订单状态
//            startAsyncQueryPayResultThread(orderId);

            return Result.ok(jxWxPayVo);
        } catch (WxPayException e) {
            e.printStackTrace();
            return Result.error(e.getMessage());
        }
    }

    /**
     * 由微信支付平台的服务调用 此接口，将微信支付结果推送给 点餐系统后端服务；
     * @param request
     * @return
     * @throws WxPayException
     */
    @ApiOperation("/回调")
    @PostMapping("/notify")
    public Result<?> notifyHandle(HttpServletRequest request) throws WxPayException {
        String requestBody = getRequestBody(request);
        log.info("回调参数：{}", requestBody);
        SignatureHeader signatureHeader = new SignatureHeader();
        signatureHeader.setNonce(request.getHeader("Wechatpay-Nonce"));
        signatureHeader.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
        signatureHeader.setSignature(request.getHeader("Wechatpay-Signature"));
        signatureHeader.setSerial(request.getHeader("Wechatpay-Serial"));
        log.info("回调 header：{}", JSON.toJSONString(signatureHeader));
        WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = payService.parseOrderNotifyV3Result(requestBody, signatureHeader);
        log.info("回调 wxPayOrderNotifyV3Result：{}", JSON.toJSONString(wxPayOrderNotifyV3Result.getResult()));
        // 根据支付回调结果，若支付成功，则将订单状态从 toPay 修改为 toSend
        handleOrderNotify(wxPayOrderNotifyV3Result);
        return Result.ok();
    }


    @ApiOperation("查询订单的支付状态")
    @RequestMapping("/queryOrderPayResult")
    public Result<?> queryOrderPayResult(
            @RequestParam("orderId") String orderId) {
        WxPayOrderQueryV3Result payOrderQueryV3Result = wxPayServiceImpl.queryPayResult(orderId);
        return Result.ok(payOrderQueryV3Result);
    }

    /**
     * 接口2.4
     * 此接口从 redis中获取之前下单时，调用 "发起支付接口" 获取的 预支付信息，继续复用之前的交易信息，当前调用 “发起支付接口”时，设置的 预支付信息有效期为 1天，因此在一天之内，可以复用，不需要再次调用接口，获取新的 预支付信息；
     * 若redis中没有，则重新调用微信支付的 发起支付接口，重新获取一次 预支付信息
     * @param orderId
     * @return
     * @throws WxPayException
     */
    @ApiOperation("已经下单，但是终止了支付，此接口用来继续之前未完成的支付过程")
    @RequestMapping("/toPay")
    public Result<?> toPay(
            @RequestParam("orderId") String orderId) throws WxPayException {

        String prepayInfo = (String)redisUtil.get(JxRedisConstant.JX_ORDER_PREPAY_INFO + orderId);
        if (prepayInfo == null || prepayInfo.isEmpty()) {
            // 若redis中找不到 预支付信息，则重新 调用下单接口，获取新的预支付信息
            JxOrderDo jxOrderDo = jxOrderMapper.selectById(orderId);
            JxUserDo jxUserDo = jxUserMapper.selectById(jxOrderDo.getUserId());
            JxWxPayVo jxWxPayVo = wxPayServiceImpl.pay( jxOrderDo.getUserId(), jxUserDo.getOpenId(), orderId,"ppm的红包",  1);
            prepayInfo = JSONObject.toJSONString(jxWxPayVo);
        }
//        startAsyncQueryPayResultThread(orderId);
        return Result.ok(prepayInfo);
    }

    /**
     * 当接收到微信支付平台的 回调消息时，从回调消息中解析出 支付结果，若支付成功，则将订单状态从 待支付，更新成 已支付待配送
     * @param wxPayOrderNotifyV3Result
     */
    public void handleOrderNotify(WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result) {
        if (HcWxPayStateEnum.SUCCESS.getValue().equals(wxPayOrderNotifyV3Result.getResult().getTradeState())) {
            String orderId = wxPayOrderNotifyV3Result.getResult().getOutTradeNo();
            log.info("订单{} 支付状态为 {}，支付完成，支付详细结果: {}", orderId, wxPayOrderNotifyV3Result.getResult().getTradeState(), wxPayOrderNotifyV3Result);
            // 将 jx_order 和 jx_order_detail 表中的订单状态改成 toSend，已支付待发货
            jxOrderServiceImpl.updateOrderState(orderId, JxOrderStateEnum.TOSEND);
            // 删除redis中缓存的 预支付信息
            redisUtil.del(JxRedisConstant.JX_ORDER_PREPAY_INFO + orderId);
        } else {
            log.error("支付失败，结果 {}", wxPayOrderNotifyV3Result);
        }
    }
    /**
     * 读取请求数据流
     *
     * @param request
     * @return
     */
    private String getRequestBody(HttpServletRequest request) {
        StringBuffer sb = new StringBuffer();

        try (ServletInputStream inputStream = request.getInputStream();
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
            String line;

            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }

        } catch (IOException e) {
            log.error("读取数据流异常:{}", e);
        }

        return sb.toString();
    }

    /**
     * 取消订单，取消订单后，用户支付的费用会原路返回；取消后的订单，用户可以再次下单
     * @return
     */
    @GetMapping("/cancelOrder")
    public Result<?> cancelOrder(
            @RequestParam("orderId") String orderId) throws WxPayException {

        JxOrderDo jxOrderDo = jxOrderMapper.selectById(orderId);
        if (jxOrderDo != null) {
            if (jxOrderDo.getState() == JxOrderStateEnum.TOPAY) {
                // 取消待支付的订单，即用户还没有支付，则只需要只需要调用微信接口，取消 支付交易；
                abortPay(orderId);
                // 然后 将订单状态更新为 canceled 即可
                jxOrderDo.setId(orderId);
                jxOrderDo.setState(JxOrderStateEnum.CANCELED);
            } else if (jxOrderDo.getState() ==JxOrderStateEnum.TOSEND) {
                // 状态为 待发货的订单，需要退款
                wxPayServiceImpl.refund(orderId, "", 1, 1); // todo 需要为退款请求的结果做验证
                // 并将状态修改成 canceled
                jxOrderDo.setId(orderId);
                jxOrderDo.setState(JxOrderStateEnum.CANCELED);
            } else {
                return Result.error("当前订单状态为：" + jxOrderDo.getState().getName() + " 取消订单失败");
            }

            JxOrderDo jxOrderDo2 = new JxOrderDo();
            jxOrderDo2.setId(orderId);
            jxOrderDo2.setState(JxOrderStateEnum.CANCELED);
            int updateCount = jxOrderMapper.updateById(jxOrderDo2);
            if (updateCount == 1) {
                return Result.ok("订单取消成功，可以在全部订单中查看已取消的订单，并再次下单");
            } else {
                return Result.error("订单取消失败，请联系客服人员进行处理");
            }
        } else {
            return Result.error("订单不存在，无法取消，请联系客户工作人员进行处理");
        }
    }

    /**
     * 接口3.17 不建议显示 【取消支付】按钮，此接口已实现，暂时不对小程序端开发，暂时不使用
     * 使用场景
     * 当用户下单后，小程序端唤起支付后，用户没有支付，然后用户退出 创建订单页面；此时订单处于 未支付状态，此订单上可以显示显示【取消支付】 按钮
     * @param orderId
     * @return
     */
    public Result<?> abortPay(
            @RequestParam("orderId") String orderId) {

        return wxPayServiceImpl.closeTransaction(orderId);
    }

    /**
     * 由于之前后端服务未启用https，使用的是http协议，传递给 微信支付平台的 notify接口，是 https://btprint.yongshantuan.cn:8085/xxxxx；
     * 因此 微信支付平台无法 与后端服务建立https链接，无法将支付结果通过回调接口返回给 后端服务
     * 本方法启动一个异步线程，每隔5秒，调用查询接口，查询订单支付状态，使用轮询的方式来暂时替代 回调的方式
     * @param orderId
     */
    private void startAsyncQueryPayResultThread(String orderId) {
        // 启动一个线程，每隔 1秒 查询一次订单支付结果，持续60次，查询1分钟
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int k = 0 ; k < 5 * 60 ; k ++) {
                    Result<?> payResult = queryOrderPayResult(orderId);
                    if (payResult.getResult() != null) {
                        WxPayOrderQueryV3Result wxPayOrderQueryV3Result = (WxPayOrderQueryV3Result)payResult.getResult();
                        if (wxPayOrderQueryV3Result.getTradeState().trim().equalsIgnoreCase("SUCCESS")) {
                            log.info("订单{} 支付状态为 {}，支付完成，支付详细结果: {}", orderId, wxPayOrderQueryV3Result.getTradeState(), wxPayOrderQueryV3Result);
                            // 将 jx_order 和 jx_order_detail 表中的订单状态改成 toSend，已支付待发货
                            jxOrderServiceImpl.updateOrderState(orderId, JxOrderStateEnum.TOSEND);
                            // 删除redis中缓存的 预支付信息
                            redisUtil.del(JxRedisConstant.JX_ORDER_PREPAY_INFO + orderId);
                            return;
                        } else {
                            log.info("订单{} 支付状态为 {}, {}，未完成支付，等待1秒再次查询", orderId, wxPayOrderQueryV3Result.getTradeState(), wxPayOrderQueryV3Result.getTradeStateDesc());
                            try {
                                Thread.sleep(5000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    } else {
                        log.error("查询订单{} 支付结果失败，查询结果为null，等待10秒后再次查询", orderId);
                        try {
                            Thread.sleep(10000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();
    }

}
